home *** CD-ROM | disk | FTP | other *** search
- /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
- * ***** BEGIN LICENSE BLOCK *****
- * Version: MPL 1.1/GPL 2.0/LGPL 2.1
- *
- * The contents of this file are subject to the Mozilla Public License Version
- * 1.1 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- * http://www.mozilla.org/MPL/
- *
- * Software distributed under the License is distributed on an "AS IS" basis,
- * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- * for the specific language governing rights and limitations under the
- * License.
- *
- * The Original Code is Mozilla Calendar code.
- *
- * The Initial Developer of the Original Code is
- * Jussi Kukkonen <jussi.kukkonen@welho.com>.
- * Portions created by the Initial Developer are Copyright (C) 2004
- * the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Michiel van Leeuwen <mvl@exedo.nl>
- * Ernst Herbst <hb@calen.de>
- *
- * Alternatively, the contents of this file may be used under the terms of
- * either the GNU General Public License Version 2 or later (the "GPL"), or
- * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- * in which case the provisions of the GPL or the LGPL are applicable instead
- * of those above. If you wish to allow use of your version of this file only
- * under the terms of either the GPL or the LGPL, and not to allow others to
- * use your version of this file under the terms of the MPL, indicate your
- * decision by deleting the provisions above and replace them with the notice
- * and other provisions required by the GPL or the LGPL. If you do not delete
- * the provisions above, a recipient may use your version of this file under
- * the terms of any one of the MPL, the GPL or the LGPL.
- *
- * ***** END LICENSE BLOCK ***** */
-
- // Import
-
- function calOutlookCSVImporter() {
- this.wrappedJSObject = this;
- }
-
- calOutlookCSVImporter.prototype.QueryInterface =
- function QueryInterface(aIID) {
- if (!aIID.equals(Components.interfaces.nsISupports) &&
- !aIID.equals(Components.interfaces.calIImporter)) {
- throw Components.results.NS_ERROR_NO_INTERFACE;
- }
-
- return this;
- };
-
- function getOutlookCsvFileTypes(aCount) {
- aCount.value = 1;
- var wildmat = '*.csv';
- var label = calGetString("calendar", 'filterOutlookCsv', [wildmat]);
- return([{defaultExtension:'csv',
- extensionFilter: wildmat,
- description: label}]);
- }
-
- calOutlookCSVImporter.prototype.getFileTypes = getOutlookCsvFileTypes;
-
- const localeEn = {
- headTitle : "Subject",
- headStartDate : "Start Date",
- headStartTime : "Start Time",
- headEndDate : "End Date",
- headEndTime : "End Time",
- headAllDayEvent : "All day event",
- headAlarm : "Reminder on/off",
- headAlarmDate : "Reminder Date",
- headAlarmTime : "Reminder Time",
- headCategories : "Categories",
- headDescription : "Description",
- headLocation : "Location",
- headPrivate : "Private",
-
- valueTrue : "True",
- valueFalse : "False",
-
- dateRe : /^(\d+)\/(\d+)\/(\d+)$/,
- dateDayIndex : 2,
- dateMonthIndex : 1,
- dateYearIndex : 3,
- dateFormat : "%m/%d/%y",
-
- timeRe : /^(\d+):(\d+):(\d+) (\w+)$/,
- timeHourIndex : 1,
- timeMinuteIndex : 2,
- timeSecondIndex : 3,
- timeAmPmIndex : 4,
- timeAmString : "AM",
- timePmString : "PM",
- timeFormat : "%I:%M:%S %p"
- };
-
- const localeNl = {
- headTitle : "Onderwerp",
- headStartDate : "Begindatum",
- headStartTime : "Begintijd",
- headEndDate : "Einddatum",
- headEndTime : "Eindtijd",
- headAllDayEvent : "Evenement, duurt hele dag",
- headAlarm : "Herinneringen aan/uit",
- headAlarmDate : "Herinneringsdatum",
- headAlarmTime : "Herinneringstijd",
- headCategories : "Categorieδn",
- headDescription : "Beschrijving",
- headLocation : "Locatie",
- headPrivate : "PrivΘ",
-
- valueTrue : "Waar",
- valueFalse : "Onwaar",
-
- dateRe : /^(\d+)-(\d+)-(\d+)$/,
- dateDayIndex : 1,
- dateMonthIndex : 2,
- dateYearIndex : 3,
- dateFormat : "%d-%m-%y",
-
- timeRe : /^(\d+):(\d+):(\d+)$/,
- timeHourIndex : 1,
- timeMinuteIndex : 2,
- timeSecondIndex : 3,
- timeFormat : "%H:%M:%S"
- };
-
- const locales = [localeEn, localeNl];
-
- // Windows line endings, CSV files with LF only can't be read by Outlook.
- const exportLineEnding = "\r\n";
-
- /**
- * Takes a text block of Outlook-exported Comma Separated Values and tries to
- * parse that into individual events.
- *
- * First line is field names, all quoted with double quotes. Field names are
- * locale dependendent. In English the recognized field names are:
- * "Title","Start Date","Start Time","End Date","End Time","All day event",
- * "Reminder on/off","Reminder Date","Reminder Time","Categories",
- * "Description","Location","Private"
- * The fields "Title" and "Start Date" are mandatory. If "Start Time" misses
- * the event is set as all day event. If "End Date" or "End Time" miss the
- * default durations are set.
- *
- * The rest of the lines are events, one event per line, with fields in the
- * order descibed by the first line. All non-empty values must be quoted.
- *
- * Returns: an array of parsed calendarEvents.
- * If the parse is cancelled, a zero length array is returned.
- */
-
- calOutlookCSVImporter.prototype.importFromStream =
- function csv_importFromStream(aStream, aCount) {
- var scriptableInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
- .createInstance(Components.interfaces.nsIScriptableInputStream);
- scriptableInputStream.init(aStream);
- var str = scriptableInputStream.read(-1);
-
- parse: {
- // parse header line of quoted comma separated column names.
- var trimEndQuotesRegExp = /^"(.*)"$/m;
- var trimResults = trimEndQuotesRegExp.exec( str );
- var header = trimResults && trimResults[1].split(/","/);
- if (header == null)
- break parse;
-
- //strip header from string
- str = str.slice(trimResults[0].length);
-
- var args = new Object();
- //args.fieldList contains the field names from the first row of CSV
- args.fieldList = header;
-
- var locale;
- var i;
- for (i in locales) {
- locale = locales[i];
- var knownIndxs = 0;
- args.titleIndex = 0;
- args.startDateIndex = 0;
- for (var i = 1; i <= header.length; ++i) {
- switch( header[i-1] ) {
- case locale.headTitle: args.titleIndex = i; knownIndxs++; break;
- case locale.headStartDate: args.startDateIndex = i; knownIndxs++; break;
- case locale.headStartTime: args.startTimeIndex = i; knownIndxs++; break;
- case locale.headEndDate: args.endDateIndex = i; knownIndxs++; break;
- case locale.headEndTime: args.endTimeIndex = i; knownIndxs++; break;
- case locale.headAllDayEvent: args.allDayIndex = i; knownIndxs++; break;
- case locale.headAlarm: args.alarmIndex = i; knownIndxs++; break;
- case locale.headAlarmDate: args.alarmDateIndex = i; knownIndxs++; break;
- case locale.headAlarmTime: args.alarmTimeIndex = i; knownIndxs++; break;
- case locale.headCategories: args.categoriesIndex = i; knownIndxs++; break;
- case locale.headDescription: args.descriptionIndex = i; knownIndxs++; break;
- case locale.headLocation: args.locationIndex = i; knownIndxs++; break;
- case locale.headPrivate: args.privateIndex = i; knownIndxs++; break;
- }
- }
- // Were both mandatory fields recognized?
- if (args.titleIndex != 0 && args.startDateIndex != 0) {
- break;
- }
- }
-
- if (knownIndxs == 0 && header.length == 22) {
- // set default indexes for a default Outlook2000 CSV file
- args.titleIndex = 1;
- args.startDateIndex = 2;
- args.startTimeIndex = 3;
- args.endDateIndex = 4;
- args.endTimeIndex = 5;
- args.allDayIndex = 6;
- args.alarmIndex = 7;
- args.alarmDateIndex = 8;
- args.alarmTimeIndex = 9;
- args.categoriesIndex = 15;
- args.descriptionIndex = 16;
- args.locationIndex = 17;
- args.privateIndex = 20;
- }
-
- if (args.titleIndex == 0 || args.startDateIndex == 0) {
- dump("Can't import. Life sucks\n")
- break parse;
- }
-
- // Construct event regexp according to field indexes. The regexp can
- // be made stricter, if it seems this matches too loosely.
- var regExpStr = "^";
- for (i = 1; i <= header.length; i++) {
- if (i > 1)
- regExpStr += ",";
- regExpStr += "(?:\"((?:[^\"]|\"\")*)\")?";
- }
- regExpStr += "$";
-
- // eventRegExp: regexp for reading events (this one'll be constructed on fly)
- const eventRegExp = new RegExp(regExpStr, "gm");
-
- // match first line
- var eventFields = eventRegExp(str);
-
- if (eventFields == null)
- break parse;
-
- args.boolStr = localeEn.valueTrue;
- args.boolIsTrue = true;
-
- var dateParseConfirmed = false;
- var eventArray = new Array();
- do {
- // At this point eventFields contains following fields. Position
- // of fields is in args.[fieldname]Index.
- // subject, start date, start time, end date, end time,
- // all day, alarm on, alarm date, alarm time,
- // Description, Categories, Location, Private
- // Unused fields (could maybe be copied to Description):
- // Meeting Organizer, Required Attendees, Optional Attendees,
- // Meeting Resources, Billing Information, Mileage, Priority,
- // Sensitivity, Show time as
-
- var title = ("titleIndex" in args
- ? parseTextField(eventFields[args.titleIndex]) : "");
- var sDate = parseDateTime(eventFields[args.startDateIndex],
- eventFields[args.startTimeIndex],
- locale);
- var eDate = parseDateTime(eventFields[args.endDateIndex],
- eventFields[args.endTimeIndex],
- locale);
- // Create an event only if we have a startDate. No more checks
- // on sDate needed in the following process.
- if (sDate) {
- var event = createEvent();
-
- // Use column head in brackets if event title misses in data.
- if (title) {
- event.title = title;
- } else {
- event.title = "[" + locale.headTitle + "]";
- }
-
- // Check data for all day event. Additionally sDate.isDate
- // may have been set in parseDateTime() if no time was found
- if (eventFields[args.allDayIndex] == locale.valueTrue) {
- sDate.isDate = true;
- }
- if (locale.valueTrue == eventFields[args.privateIndex])
- event.privacy = "PRIVATE";
-
- if (!eDate) {
- // No endDate was found. All day events last one day and
- // timed events last the default length.
- eDate = sDate.clone();
- if (sDate.isDate) {
- // end date is exclusive, so set to next day after start.
- eDate.day += 1;
- } else {
- eDate.minute += getPrefSafe(
- "calendar.event.defaultlength", 60);
- }
- } else {
- // An endDate was found.
- if (sDate.isDate) {
- // A time part for the startDate is missing or was
- // not recognized. We have to throw away the endDates
- // time part too for obtaining a valid event.
- eDate.isDate = true;
- // Correct the eDate if duration is less than one day.
- if (1 > eDate.subtractDate(sDate).days) {
- eDate = sDate.clone();
- eDate.day += 1;
- }
- } else {
- // We now have a timed startDate and an endDate. If the
- // end time is invalid set it to 23:59:00
- if (eDate.isDate) {
- eDate.isDate = false;
- eDate.hour = 23;
- eDate.minute = 59;
- }
- // Correct the duration to 0 seconds if it is negative.
- if (eDate.subtractDate(sDate).isNegative ) {
- eDate = sDate.clone();
- }
- }
- }
- event.startDate = sDate;
- event.endDate = eDate;
-
- // Exists an alarm true/false column?
- if ("alarmIndex" in args) {
- // Is an alarm wanted for this event?
- if (locale.valueTrue == eventFields[args.alarmIndex]) {
- var alarmDate =
- parseDateTime(eventFields[args.alarmDateIndex],
- eventFields[args.alarmTimeIndex],
- locale);
- // Set to default if non valid alarmDate was achieved
- if (alarmDate) {
- event.alarmOffset = alarmDate.subtractDate(sDate);
- } else {
- var alarmOffset = Components
- .classes["@mozilla.org/calendar/duration;1"]
- .createInstance(Components
- .interfaces.calIDuration);
- var units = getPrefSafe("calendar.alarms.eventalarmunit",
- "minutes");
- alarmOffset[units] = getPrefSafe("calendar.alarms.eventalarmlen",
- 15);
- alarmOffset.isNegative = true;
- event.alarmOffset = alarmOffset;
- }
- event.alarmRelated = Components.interfaces.calIItemBase
- .ALARM_RELATED_START;
- }
- }
-
- // Using the "Private" field only for getting privacy status.
- // "Sensitivity" is neglected for now.
- if ("privateIndex" in args) {
- if (locale.valueTrue == eventFields[args.privateIndex]) {
- event.privacy = "PRIVATE";
- }
- }
-
- // Avoid setting empty properties
- var txt = "";
- if ("descriptionIndex" in args) {
- txt = parseTextField(eventFields[args.descriptionIndex])
- if (txt) {
- event.setProperty("DESCRIPTION", txt);
- }
- }
- if ("categoriesIndex" in args) {
- txt = parseTextField(eventFields[args.categoriesIndex])
- if (txt) {
- event.setProperty("CATEGORIES", txt);
- }
- }
- if ("locationIndex" in args) {
- txt = parseTextField(eventFields[args.locationIndex])
- if (txt) {
- event.setProperty("LOCATION", txt);
- }
- }
-
- //save the event into return array
- eventArray.push(event);
- }
-
- //get next events fields
- eventFields = eventRegExp(str);
-
- } while (eventRegExp.lastIndex != 0);
-
- // return results
- aCount.value = eventArray.length;
- return eventArray;
-
- } // end parse
-
- aCount.value = 0;
- return new Array();
- };
-
- function parseDateTime(aDate, aTime, aLocale)
- {
- var date = Components.classes["@mozilla.org/calendar/datetime;1"]
- .createInstance(Components.interfaces.calIDateTime);
-
- //XXX Can we do better?
- date.timezone = floating();
-
- var rd = aLocale.dateRe.exec(aDate);
- var rt = aLocale.timeRe.exec(aTime);
-
- if (!rd || !rt) {
- return null;
- }
-
- date.year = rd[aLocale.dateYearIndex];
- date.month = rd[aLocale.dateMonthIndex] - 1;
- date.day = rd[aLocale.dateDayIndex];
- if (rt) {
- date.hour = Number(rt[aLocale.timeHourIndex]);
- date.minute = rt[aLocale.timeMinuteIndex];
- date.second = rt[aLocale.timeSecondIndex];
- } else {
- date.isDate = true;
- }
-
- if (rt && aLocale.timeAmPmIndex)
- if (rt[aLocale.timeAmPmIndex] != aLocale.timePmString) {
- // AM
- if (date.hour == 12)
- date.hour = 0;
- } else {
- // PM
- if (date.hour < 12)
- date.hour += 12;
- }
-
- dump(date+"\n");
- return date;
- }
-
- function parseTextField(aTextField)
- {
- return aTextField ? aTextField.replace(/""/g, "\"") : "";
- }
-
-
- // Export
-
- function calOutlookCSVExporter() {
- this.wrappedJSObject = this;
- }
-
- calOutlookCSVExporter.prototype.QueryInterface =
- function QueryInterface(aIID) {
- if (!aIID.equals(Components.interfaces.nsISupports) &&
- !aIID.equals(Components.interfaces.calIExporter)) {
- throw Components.results.NS_ERROR_NO_INTERFACE;
- }
-
- return this;
- };
-
- calOutlookCSVExporter.prototype.getFileTypes = getOutlookCsvFileTypes;
-
- // not prototype.export. export is reserved.
- calOutlookCSVExporter.prototype.exportToStream =
- function csv_exportToStream(aStream, aCount, aItems) {
- var str = "";
- var headers = [];
- // Not using a loop here, since we need to be sure the order here matches
- // with the orders the field data is added later on
- headers.push(localeEn['headTitle']);
- headers.push(localeEn['headStartDate']);
- headers.push(localeEn['headStartTime']);
- headers.push(localeEn['headEndDate']);
- headers.push(localeEn['headEndTime']);
- headers.push(localeEn['headAllDayEvent']);
- headers.push(localeEn['headAlarm']);
- headers.push(localeEn['headAlarmDate']);
- headers.push(localeEn['headAlarmTime']);
- headers.push(localeEn['headCategories']);
- headers.push(localeEn['headDescription']);
- headers.push(localeEn['headLocation']);
- headers.push(localeEn['headPrivate']);
- headers = headers.map(function(v) {
- return '"' + v + '"';
- });
- str = headers.join(',');
- str += exportLineEnding;
- aStream.write(str, str.length);
-
- for each (item in aItems) {
- if (!isEvent(item)) {
- // XXX TODO: warn the user (once) that tasks are not supported
- // (bug 336175)
- continue;
- }
- var line = [];
- line.push(item.title);
- line.push(dateString(item.startDate));
- line.push(timeString(item.startDate));
- line.push(dateString(item.endDate));
- line.push(timeString(item.endDate));
- line.push(item.startDate.isDate ? localeEn.valueTrue : localeEn.valueFalse);
- if (item.alarmOffset) {
- line.push(localeEn.valueTrue);
- var fireTime;
- if (item.alarmRelated == Components.interfaces.calIItemBase.ALARM_RELATED_START) {
- fireTime = item.startDate.clone();
- } else {
- fireTime = item.endDate.clone();
- }
- fireTime.addDuration(item.alarmOffset);
- line.push(dateString(fireTime));
- line.push(timeString(fireTime));
- } else {
- line.push(localeEn.valueFalse);
- line.push("");
- line.push("");
- }
- line.push(txtString(item.getProperty("CATEGORIES")));
- line.push(txtString(item.getProperty("DESCRIPTION")));
- line.push(txtString(item.getProperty("LOCATION")));
- line.push((item.privacy=="PRIVATE") ? localeEn.valueTrue : localeEn.valueFalse);
-
- line = line.map(function(v) {
- v = String(v).replace(/"/g,'""');
- return '"'+v+'"';
- })
- str = line.join(',') + exportLineEnding;
- aStream.write(str, str.length);
- }
-
- return;
- };
-
- function dateString(aDateTime) {
- return aDateTime.jsDate.toLocaleFormat(localeEn.dateFormat);
- }
-
- function timeString(aDateTime) {
- return aDateTime.jsDate.toLocaleFormat(localeEn.timeFormat);
- }
-
- function txtString(aString) {
- if (aString) {
- return aString;
- } else {
- return "";
- }
- }
-